#include "../encrypt/md5.h"

namespace Pdf
{
	inline Merger::Inserter::Inserter(InputDocument *document, Merger *merger)
		: document(document), merger(merger)
	{    }

	inline void Merger::Inserter::visited(Array *obj)
	{
		for (Array::Iterator i = obj->begin(), end = obj->end(); i != end; ++i)
			if ( *i != 0 )
				(*i)->apply( this );
	}

	inline void Merger::Inserter::visited(Names *obj)
	{
		for (Array::Iterator i = obj->begin(), end = obj->end(); i != end; ++i)
			if ( *i != 0 )
				(*i)->apply( this );
	}

	inline void Merger::Inserter::visited(Dictionary *obj)
	{
		for (Dictionary::Iterator i = obj->begin(), end = obj->end(); i != end; ++i)
			if ( i->first != "Parent" && i->second != 0 )
				i->second->apply( this );
	}

	inline void Merger::Inserter::visited(Reference *ref)
	{
		Reference::Value oldref( ref->get() );

		// =====================================================================
		// check if this object already has been written
		ReferenceMap::const_iterator i = inserted.find( oldref );
		if ( i != inserted.end() )
		{
			ref->set( i->second );
			return;
		}

		// =====================================================================
		// look up in caches
		IndirectObject obj = document->getIndirectObject( ref );

		// search in font cache
		FontInfo fi;
		Dictionary* dictObj = cast<Dictionary*>( obj.object );
		if ( dictObj )
		{
			Name::Ptr type = dictObj->get<Name>("Type");
			if (type && type->get() == "Font")
			{
				Name::Ptr name = dictObj->get<Name>("BaseFont");
				if ( name != 0 )
					fi.name = name->get();
				Name::Ptr encoding = dictObj->get<Name>("Encoding");
				if ( encoding != 0 )
					fi.encoding = encoding->get();
				Array::Ptr widths = dictObj->get<Array>("Widths");
				if ( widths != 0 )
				{
					fi.widths.reserve( widths->count() );
					for (Array::Iterator i = widths->begin(), end = widths->end(); i != end; ++i)
						fi.widths.push_back( static_cast<int>( cast<Integer::Value>(*i) ) );
				}

				FontCache::const_iterator cachedFont = merger->fontCache.find( fi );
				if ( cachedFont != merger->fontCache.end() )
				{
					ref->set( cachedFont->second );
					inserted[oldref] = cachedFont->second;
					return;
				}
			}
		}

		// search in stream cache
		StreamInfo si;
		if ( !obj.stream.empty() )
		{
			std::vector<char> checksum;
			RSA::MD5( obj.stream ).finish( checksum );

			si = StreamInfo( obj.stream.size(), checksum );

			StreamCache::const_iterator cachedStream = merger->streamCache.find( si );
			if ( cachedStream != merger->streamCache.end() )
			{
				ref->set( cachedStream->second );
				inserted[oldref] = cachedStream->second;
				return;
			}
		}

		// =====================================================================
		// Write referenced objects
		obj.object->apply( this );

		// =====================================================================
		// Write object
		Reference::Value newref( merger->getNextID() );
		ref->set( newref );
		merger->write( obj );

		// =====================================================================
		// Append stream info to document cache
		if ( !obj.stream.empty() )
			merger->streamCache[si] = newref;

		// =====================================================================
		// Append font info to the document cache
		if ( !fi.name.empty() )
			merger->fontCache[fi] = newref;

		// =====================================================================
		// Mark object as writen
		inserted[oldref] = newref;
	}
}